package com.tianji.learning.service.impl;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mq.message.SignMessage;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.tianji.common.utils.UserContext;


import static com.tianji.common.constants.MqConstants.Exchange.LEARNING_EXCHANGE;
import static com.tianji.common.constants.MqConstants.Key.SIGN_IN;
import static com.tianji.common.utils.DateUtils.DATE_SUFFIX_FORMATTER;
import static com.tianji.learning.constants.RedisConstants.SIGN_RECORD_KEY_PREFIX;


@Slf4j
@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper mqHelper;

    @Override
    public SignResultVO addSignRecords() {
        // 1.签到
        // 1.1.获取签到key
        String key = getSignKey();

        // 1.2.计算offset值
        int offset = LocalDate.now().getDayOfMonth() - 1;


        // 1.3.查询key是否已经存在
        Long keyCount = redisTemplate.countExistingKeys(Collections.singleton(key));
        boolean keyExists = false;
        if (ObjectUtils.isNull(keyCount) && keyCount > 0){
            keyExists = true;
    }

        // 1.4.签到并判断是否是重复签到
        Boolean isRepeat = redisTemplate.opsForValue().setBit(key, offset, true);
        if (BooleanUtils.isTrue(isRepeat)) {
            throw new BizIllegalException("请勿重复签到");
        }


        // 1.5.如果之前key是不存在的，证明是本月第一次签到，需要设置过期时间
        if (keyExists) {
            LocalDateTime begin = LocalDateTime.now();
            LocalDateTime end = DateUtils.getMonthEndTime(LocalDate.now());

            Long senconds = LocalDateTimeUtil.between(begin, end, ChronoUnit.SECONDS);
            redisTemplate.expire(key, senconds, TimeUnit.SECONDS);

            Duration duration = LocalDateTimeUtil.between(begin, end);
            redisTemplate.expire(key, duration);
        }

        // 2.计算连续签到天数
        int signDays = countSignDays(key);


        // 3.计算签到得分
        int rewardPoint = computerRewardPoints(signDays);


        // 4.构建vo
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(rewardPoint);

        //5、发送保存积分明细的消息（埋点发消息）
        try {
            mqHelper.send(LEARNING_EXCHANGE, SIGN_IN, SignMessage.of(UserContext.getUser(), vo.totalPoints()));
        } catch (Exception e) {
            log.error("发送积分明细消息失败，原因：{}", e);
        }


        // 6.返回结果
        return vo;
    }

    @Override
    public Byte[] querySignRecords() {
        // 1.获取签到key
        String key = getSignKey();

        // 2.通过bitfield命令，获取节制到当天为止的签到结果（十进制）
        int dayOfMonth = LocalDate.now().getDayOfMonth();
        List<Long> result = redisTemplate.opsForValue().bitField(key,
                BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        if(ObjectUtils.isEmpty(result)){
            return new Byte[]{};
        }
        int signResult = result.get(0).intValue();


        //3.用签到结果与1不断做与运算，将运算结果存放到Byte数组
        Byte[] bytes = new Byte[dayOfMonth];
        int index = dayOfMonth - 1;
        while (index >= 0) {
            bytes[index--] = (byte) (signResult & 1);
            signResult >>>= 1;
        }
        return bytes;
    }

    private int computerRewardPoints(int signDays) {
        switch (signDays) {
            case 7:
                return 10;
            case 14:
                return 20;
            case 28:
                return 40;
            default:
                return 0;
        }
    }



    private int countSignDays(String key) {
        //1、通过bitfield命令，获取节制到当天为止的签到结果（十进制）
        int dayOfMonth = LocalDate.now().getDayOfMonth();
        List<Long> result = redisTemplate.opsForValue().bitField(key,
                BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0)
        );
        if(ObjectUtils.isEmpty(result)){
            return 0;
        }
        int signResult = result.get(0).intValue();
        //2、定义一个计数器
        int count = 0;


        //3、用签到结果跟1不断做与运算，判断是否等于1，如果等于1，则计数器加1
        while ((signResult & 1) == 1) {
            //3.1、签到成功，计数器加1

            count++;

            //3.2、将签到结果右移一位
            signResult >>>= 1;

        }
        return count;

    }


    private String getSignKey() {
        // 1.获取用户id
        Long userId = UserContext.getUser();

        // 2.获取当前年月
        String dateStr = LocalDate.now().format(DATE_SUFFIX_FORMATTER);

        // 3.拼接key
        return SIGN_RECORD_KEY_PREFIX + userId + dateStr;
    }
}